﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace HashFoo.Core
{
    /// <summary>
    /// Provides a full class version of an enumeration.  Allows for greatness like looking up an enumeration by value or display name.
    /// </summary>
    /// <typeparam name="TEnum">The type of enumeration (the inheriting class)</typeparam>
    /// <typeparam name="TValue">The value type for the ID of the enumeration</typeparam>
    public abstract class EnumerationBase<TEnum, TValue> : IComparable
        where TValue : IComparable
        where TEnum : EnumerationBase<TEnum, TValue>
    {
        private readonly TValue _value;
        private readonly string _displayName;

        /// <summary>
        /// Useful for serialization, I think.
        /// </summary>
        private EnumerationBase() { }

        /// <summary>
        /// Create a new enumeration
        /// </summary>
        /// <param name="value"></param>
        /// <param name="displayName"></param>
        protected EnumerationBase(TValue value, string displayName)
            : this()
        {
            _value = value;
            _displayName = displayName;
        }

        /// <summary>
        /// The value of the enumeration.
        /// </summary>
        public TValue Value
        {
            get { return _value; }
        }

        /// <summary>
        /// The display name of the enumeration.
        /// </summary>
        public string DisplayName
        {
            get { return _displayName; }
        }

        /// <summary>
        /// Returns the display name.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return DisplayName;
        }

        /// <summary>
        /// Get all the enumerations of <c>TEnum</c> type.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TEnum> GetAll()
        {
            var type = typeof(TEnum);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            return (from info in fields
                    select info.GetValue(null)).OfType<TEnum>();
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. </param>
        public override bool Equals(object obj)
        {
            var otherValue = obj as EnumerationBase<TEnum, TValue>;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }

        /// <summary>
        /// Gets the <c>TEnum</c> from a specific value.  Throws <see cref="ArgumentOutOfRangeException"/> if not found.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static TEnum FromValue(TValue value)
        {
            var matchingItem = Parse(value, "value", item => item.Value.Equals(value));
            return matchingItem;
        }

        /// <summary>
        /// Gets a TEnum from a display name.  Throws <see cref="ArgumentOutOfRangeException"/> if not found.
        /// </summary>
        /// <param name="displayName"></param>
        /// <returns></returns>
        public static TEnum FromDisplayName(string displayName)
        {
            var matchingItem = Parse(displayName, "display name", item => item.DisplayName == displayName);
            return matchingItem;
        }

        private static TEnum Parse<TK>(TK value, string description, Func<TEnum, bool> predicate)
        {
            var matchingItem = GetAll().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(TEnum));
                throw new ArgumentOutOfRangeException("value", message);
            }

            return matchingItem;
        }

        /// <summary>
        /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance is less than <paramref name="other"/>. Zero This instance is equal to <paramref name="other"/>. Greater than zero This instance is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this instance. </param><exception cref="T:System.ArgumentException"><paramref name="other"/> is not the same type as this instance. </exception>
        public virtual int CompareTo(object other)
        {
            return Value.CompareTo(((EnumerationBase<TEnum, TValue>)other).Value);
        }

        /// <summary>
        /// Gets the <typeparamref name="TEnum"/> from a value.  Null if not found.
        /// </summary>
        /// <param name="enumerationValue"></param>
        /// <returns></returns>
        public static TEnum FromValueOrDefault(int enumerationValue)
        {
            return GetAll().SingleOrDefault(e => e.Value.Equals(enumerationValue));
        }

        /// <summary>
        /// Gets the <typeparamref name="TEnum"/> from a display name.  Null if not found.
        /// </summary>
        /// <param name="displayName"></param>
        /// <returns></returns>
        public static TEnum FromDisplayNameOrDefault(string displayName)
        {
            return GetAll().SingleOrDefault(e => e.DisplayName == displayName);
        }
    }
}

